#include "util.hpp"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

class ServerTcp; // 声明一下ServerTcp

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;
public:
    ThreadData(uint16_t port, std::string ip, int sock,  ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {}
};

class ServerTcp
{
public:
    // 文件描述符默认是大于0的，-1就是没有被设置
    ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);

        // 2. bind绑定
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));

        // 2.2 本地socket信息，写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);

        // 3. 监听socket，为何要监听呢？因为udp是无连接的(通信可以，但是不用建立连接，直接发消息就可以了)，而tcp是面向连接的！
        // 面向就是在做任何事之前要先干什么这就是面向的意思，面向连接就是在做其他工作之前先把连接建立好。
        // 不管有没有客户端连接，得让服务器将来任何时候被别人连接，所以要将套接字设置成监听状态。
        if (listen(listenSock_, 5) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
        // 走到这就意味着允许别人来连接你了
    }

    // 类内方法，形参默认带有this指针
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self()); // 设置线程分离
        ThreadData *td = static_cast<ThreadData*>(args);
        td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
        delete td;
        return nullptr;
    }

    void loop()
    {
        // 不用等子进程了
        // signal(SIGCHLD, SIG_IGN); // only Linux

        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            // 4. 获取连接, accept 的返回值是一个新的socket fd ？？
            // 4.1 listenSock_: 监听 && 获取新的链接-> sock
            // 4.2 serviceSock: 给用户提供新的socket服务
            // serviceSock给客户提供服务的套接字
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                // 获取链接失败
                logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
                continue;
            }

            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // // 5 提供服务, echo -> 小写 -> 大写
            // // 5.0 v0 版本 -- 单进程 -- 一旦进入transService，主执行流，就无法进行向后执行，只能提供完毕服务之后才能进行accept
            // transService(serviceSock, peerIp, peerPort);
            
            // // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗？会的！
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     // 子进程也会把曾经父进程打开的listen套接字也继承下去
            //     close(listenSock_); // 建议关掉

            //     // 子进程 -- 通过创建子进程，让其做父进程代码的一部分                
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0); // 任务处理完就退出，进入僵尸
            // }
            // // 父进程 -- 父进程不用对外提供服务
            // close(serviceSock); // 这一步是一定要做的！
            // // waitpid(); 默认是阻塞等待！WNOHANG

            // // 5.1 v1.1 版本 -- 多进程版本  -- 这样写也是可以的
            // // 爷爷进程
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // 爸爸进程
            //     close(listenSock_);// 建议关掉
            //     // 又进行了一次fork，让 爸爸进程
            //     if(fork() > 0) exit(0);

            //     // 孙子进程 -- 就没有爸爸 -- 就变成了孤儿进程 -- 被系统领养 -- 孙子进程就交给了系统来回收
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0);
            // }
            // // 父进程
            // close(serviceSock); // 这一步是一定要做的！
            // // 爸爸进程直接终止，立马得到退出码，释放僵尸进程状态
            // pid_t ret = waitpid(id, nullptr, 0); // 就用阻塞式
            // assert(ret > 0);
            // (void)ret;

            // 5.2 v2 版本 -- 多线程
            // 这里不需要进行关闭文件描述符吗？？不需要啦！！
            // 多线程是会共享文件描述符表的！
            ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, (void*)td);
            // 不可进行线程等待，一等待，主线程就阻塞了，只能用线程分离

            logMessage(DEBUG, "server 提供 service start ...");
            sleep(1);
        }
    }

    // 大小写转化服务
    // TCP && UDP: 支持全双工
    void transService(int sock, const std::string &clientIp, uint16_t clientPort)
    {
        // std::cout << "transService start...." << std::endl;
        assert(sock >= 0);
        assert(!clientIp.empty());
        assert(clientPort >= 1024);

        char inbuffer[BUFFER_SIZE];
        while (true)
        {
            // 读再多也不要读满了，留着自己添加 \0
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); // 我们认为我们读到的都是字符串
            if (s > 0)
            {
                // read success
                inbuffer[s] = '\0';
                if(strcasecmp(inbuffer, "quit") == 0)
                {
                    logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                    break;
                }
                logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
                
                // 可以进行大小写转化了
                for(int i = 0; i < s; i++)
                {
                    // isalpha用于检查一个字符是否为字母
                    // islower用于检查一个字符是否为小写字母
                    if(isalpha(inbuffer[i]) && islower(inbuffer[i])) 
                        inbuffer[i] = toupper(inbuffer[i]);
                }

                logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);

                // 再写回去
                write(sock, inbuffer, strlen(inbuffer));
            }
            else if (s == 0)
            {
                // pipe: 读端一直在读，写端不写了，并且关闭了写端，读端会如何？s == 0，代表对端关闭
                // s == 0: 代表对方关闭，client 退出
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            else
            {
                logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
                break;
            }
        }

        // 只要走到这里，一定是client退出了，服务到此结束
        close(sock); // 如果一个进程对应的文件fd，打开了没有被归还，这种现象叫做文件描述符泄漏！
        // 如果不关，来一个客户端打开一个文件描述符，会导致该服务端进程可用文件描述符越来越少
        // 文件描述符表是有上限的，时间一久，会导致服务器无法获取新连接，申请文件描述符时发现所有文件描述符都被占用了
        // 此时服务器就无法对外提供服务了
        logMessage(DEBUG, "server close %d done", sock);
    }

private:
    // sock
    int listenSock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl;

}

// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{
    if(argc != 2 && argc != 3 )
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);
    std::string ip;
    if(argc == 3) ip = argv[2];

    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();

    return 0;
}